/*
  cgifs: CGI like filesystem
  Copyright (C) 2009 Boris Sukholitko <boriss@gmail.com>

  This program can be distributed under the terms of the GNU GPL.
  See the file COPYING.

*/

#define FUSE_USE_VERSION 26

#ifdef linux
/* For pread()/pwrite() */
#define _XOPEN_SOURCE 500
#endif

#include <fuse.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
#include <dirent.h>
#include <errno.h>
#include <sys/time.h>
#include <error.h>
#include <limits.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <stdlib.h>
#include <strings.h>
#include <syslog.h>

static const char *_files_dir;
static const char *_scripts_dir;

static const char *get_path(char *buf, const char *pth) {
	struct stat st;
	pid_t pid;
	int status;
	char exe[PATH_MAX];
	char *p;
	int fd[2];
	char err[BUFSIZ];
	ssize_t res;

	snprintf(buf, PATH_MAX, "%s%s", _files_dir, pth);
	if (!stat(buf, &st))
		goto out;

	snprintf(exe, PATH_MAX, "%s%s", _scripts_dir, pth);
	if (!stat(exe, &st)) {
		mkdir(buf, S_IRWXU);
		goto out;
	}

	p = rindex(exe, '/');
	*p = '\0';
	if (pipe(fd))
		syslog(LOG_CRIT, "Unable to create pipe\n");

	pid = fork();
	if (!pid) {
		int i, omax;

		if (dup2(fd[1], STDERR_FILENO) != STDERR_FILENO)
			syslog(LOG_CRIT, "Unable to dup2 stderr\n");

		omax = 32; /* sysconf(_SC_OPEN_MAX); can have many of them */
		for (i = 0; i < omax; i++)
			if (i != STDERR_FILENO)
				close(i);

		res = execl(exe, exe, buf, NULL);
		if (res)
			syslog(LOG_CRIT, "Unable to execute %s with %s"
				, exe, buf);
		exit(res);
	}

	close(fd[1]);
	for (;;) {
		res = read(fd[0], err, BUFSIZ - 1);
		if (res <= 0)
			break;

		err[res] = '\0';
		syslog(LOG_CRIT, "Error from %s: %s\n", exe, err);
	}
	close(fd[0]);
	waitpid(pid, &status, 0);
out:
	return buf;
}

static int cgifs_getattr(const char *pth, struct stat *stbuf)
{
	char path[PATH_MAX];
	int res;

	res = lstat(get_path(path, pth), stbuf);
	if (res == -1)
		return -errno;

	return 0;
}

static int cgifs_access(const char *pth, int mask)
{
	char path[PATH_MAX];
	int res;

	res = access(get_path(path, pth), mask);
	if (res == -1)
		return -errno;

	return 0;
}

static int cgifs_open(const char *pth, struct fuse_file_info *fi)
{
	char path[PATH_MAX];
	int res;

	res = open(get_path(path, pth), fi->flags);
	if (res == -1)
		return -errno;

	close(res);
	return 0;
}

static int cgifs_read(const char *pth, char *buf, size_t size, off_t offset,
		    struct fuse_file_info *fi)
{
	int fd;
	char path[PATH_MAX];
	int res;

	(void) fi;
	fd = open(get_path(path, pth), O_RDONLY);
	if (fd == -1)
		return -errno;

	res = pread(fd, buf, size, offset);
	if (res == -1)
		res = -errno;

	close(fd);
	return res;
}

static struct fuse_operations cgifs_oper = {
	.getattr	= cgifs_getattr,
	.access		= cgifs_access,
	.open		= cgifs_open,
	.read		= cgifs_read,
};

int main(int argc, char *argv[])
{
	if (argc < 4)
		error(1, 0, "Usage: %s <mount point> <scripts dir>"
			" <files dir>\n", argv[0]);

	_files_dir = argv[argc - 1];
	_scripts_dir = argv[argc - 2];

	umask(0);
	return fuse_main(argc - 2, argv, &cgifs_oper, NULL);
}
